using IQIGame.Onigao.Game;
using System;
using System.Collections.Generic;

namespace IQIGame.Onigao.Logic
{
    public static class GeometryUtil
    {
        /// <summary>
        /// 找出旋转矩形的最小外包围矩形
        /// </summary>
        /// <param name="rectMin"></param>
        /// <param name="rectW"></param>
        /// <param name="rectH"></param>
        /// <param name="rotation"></param>
        /// <param name="pivot"></param>
        /// <returns></returns>
        public static RectLogic RotateAndFindBoundingRect(Vector2Logic rectMin, float rectW, float rectH, int rotation, Vector2Logic pivot)
        {
            if (rotation == 0)
            {
                return new RectLogic(rectMin.x, rectMin.y, rectW, rectH);
            }
            float minX = float.MaxValue;
            float maxX = float.MinValue;
            float minY = float.MaxValue;
            float maxY = float.MinValue;
            List<Vector2Logic> vertices = ListPool<Vector2Logic>.Get();
            {
                vertices.Add(new Vector2Logic(rectMin.x, rectMin.y));
                vertices.Add(new Vector2Logic(rectMin.x + rectW, rectMin.y));
                vertices.Add(new Vector2Logic(rectMin.x + rectW, rectMin.y + rectH));
                vertices.Add(new Vector2Logic(rectMin.x, rectMin.y + rectH));

                float radian = rotation * MathLogic.Deg2Rad;  // 角度转弧度
                float cos = (float)Math.Cos(radian);
                float sin = (float)Math.Sin(radian);

                for (int i = 0; i < vertices.Count; i++)
                {
                    // 将坐标移动至旋转点，旋转，然后再移动回原来的位置
                    Vector2Logic point = vertices[i] - pivot;
                    Vector2Logic newV = new Vector2Logic(point.x * cos - point.y * sin, point.x * sin + point.y * cos) + pivot;
                    // 计算旋转后的四个顶点的最小外包围矩形
                    minX = Math.Min(minX, newV.x);
                    maxX = Math.Max(maxX, newV.x);
                    minY = Math.Min(minY, newV.y);
                    maxY = Math.Max(maxY, newV.y);
                }
                ListPool<Vector2Logic>.Put(vertices);
            }

            // 返回计算出的外包围矩形
            return new RectLogic(minX, minY, maxX - minX, maxY - minY);
        }

        /// <summary>
        /// 预测圆移动时和另一个圆的第一个交点
        /// </summary>
        /// <param name="movingCircleCenter"></param>
        /// <param name="movingCircleRadius"></param>
        /// <param name="targetCircleCenter"></param>
        /// <param name="targetCircleRadius"></param>
        /// <param name="endCenterPoint">刚碰撞时，移动圆的圆心距离</param>
        /// <returns></returns>
        public static bool PredictFirstCircleIntersect(Vector2Logic movingCircleCenter, float movingCircleRadius, Vector2Logic endCenterPoint,
            Vector2Logic targetCircleCenter, float targetCircleRadius, out Vector2Logic stopPoint)
        {
            //其实可以把该问题转换成从“movingCircle圆心出发，到endPoint结束的线段”，与一个“圆心和targetCircle相同，半径=movingCircleRadius+targetCircleRadius的大圆”的交点的问题。
            //具体可以画图参考，movingCircle和targetCircle在移动路线上刚好相交的点离targeetCircle圆心的距离正好等于两个圆的半径之和。
            bool hasIntersections = false;
            List<Vector2Logic> intersections = ListPool<Vector2Logic>.Get();
            {
                IsLineSegmentIntersectCircle(movingCircleCenter, endCenterPoint, targetCircleCenter, movingCircleRadius + targetCircleRadius, intersections);
                stopPoint = default;
                if (intersections.Count > 0)
                {
                    hasIntersections = true;
                    //交点一定都在线段上，所以只需要找到距离movingCircle最近的交点即可
                    float minSqrDis = float.MaxValue;
                    for (int i = 0; i < intersections.Count; i++)
                    {
                        Vector2Logic intersection = intersections[i];
                        float sqrDis = (intersection - movingCircleCenter).sqrMagnitude;
                        if (sqrDis < minSqrDis)
                        {
                            minSqrDis = sqrDis;
                            stopPoint = intersection;
                        }
                    }
                }
                ListPool<Vector2Logic>.Put(intersections);
            }
            return hasIntersections;
        }

        /// <summary>
        /// 计算线段与圆的交点
        /// </summary>
        /// <param name="start">线段的起始点</param>
        /// <param name="end">线段的结束点</param>
        /// <param name="circleCenter">目标圆的圆心</param>
        /// <param name="radius">目标圆半径</param>
        /// <returns></returns>
        public static void IsLineSegmentIntersectCircle(Vector2Logic start, Vector2Logic end, Vector2Logic circleCenter, float radius, List<Vector2Logic> intersections)
        {
            if (intersections == null || intersections.Count > 0)
            {
                LogicLog.LogError("参数intersections必须是非空且长度为0的list。");
                return;
            }
            Vector2Logic d = end - start;
            Vector2Logic f = start - circleCenter;

            float a = Vector2Logic.Dot(d, d);
            float b = 2f * Vector2Logic.Dot(f, d);
            float c = Vector2Logic.Dot(f, f) - radius * radius;

            double discriminant = b * b - 4 * a * c;

            if (discriminant < 0)
            {
                // No intersection.
            }
            else
            {
                // ray didn't totally miss sphere,
                // so there is a solution to
                // the equation.

                discriminant = Math.Sqrt(discriminant);

                // either solution may be on or off the ray so need to test both
                // t1 is always the smaller value, because BOTH discriminant and
                // a are nonnegative.
                float t1 = (float)(-b - discriminant) / (2 * a);
                float t2 = (float)(-b + discriminant) / (2 * a);

                // 3x HIT cases:
                //          -o->             --|-->  |            |  --|->
                // Impale(t1 hit,t2 hit), Poke(t1 hit,t2>1), ExitWound(t1<0, t2 hit), 

                // 3x MISS cases:
                //       ->  o                     o ->              | -> |
                // FallShort (t1>1,t2>1), Past (t1<0,t2<0), CompletelyInside(t1<0, t2>1)

                if (t1 >= 0 && t1 <= 1)
                {
                    // t1 is the intersection, and it's closer than t2
                    // (since t1 uses -b - discriminant)
                    // Impale, Poke
                    intersections.Add(start + t1 * d);
                }

                // here t1 didn't intersect so we are either started
                // inside the sphere or completely past it
                if (t2 >= 0 && t2 <= 1)
                {
                    // ExitWound
                    intersections.Add(start + t2 * d);
                }
            }
        }

        /// <summary>
        /// 预测移动的矩形和目标矩形碰撞时的中心点位置
        /// ***注意：该方法比较贴近业务，为了优化性能，对参数有特殊要求，如果要使用，请先确定满足以下要求***
        /// ***调用该方法前，必须保证targetRect和movingRect都被变换到了以movingRect.center为原点，移动方向为y轴的坐标系***
        /// ***如果要改成通用方法，需传入移动方向，并调用两个rect的TransformByNewAxis方法进行一次转换，但如果用在循环中，不能优化转换的性能***
        /// </summary>
        /// <param name="movingRect">要移动的矩形</param>
        /// <param name="targetRect">目标矩形</param>
        /// <param name="moveDistance">移动距离</param>
        /// <param name="endCenterPoint">输出的碰撞时矩形中心的位置</param>
        /// <returns></returns>
        public static bool PredictMovingBoxCenterOnCollision(TransableVertexOBBRect movingRect, TransableVertexOBBRect targetRect, float moveDistance, out Vector2Logic endCenterPoint)
        {
            //注意该方法里的坐标系已转换到movingRect中心为原点，移动方向为y轴的坐标系
            //情况1：
            //计算targetRect里离x轴距离最近的“顶点”是否在movingRect“左右两边的延长线”之内（即移动范围内）。
            //如果在，那么碰撞时movingRect的顶边会优先碰到这个点。
            float minY = float.MaxValue;
            Vector2Logic[] targetVertices = targetRect.vertices;
            Vector2Logic minVertex = default;
            float halfHeight = (movingRect.maxX - movingRect.minY) / 2;
            //移动到最远处时，movingRect的顶边的y坐标
            float maxMoveTopY = moveDistance + halfHeight;
            //计算出来的movingRect.center相对于转换后的坐标（以movingRect中心为原点，移动方向为y轴）的值
            Vector2Logic endPointInMovingRectAxis;
            for (int i = 0; i < targetVertices.Length; i++)
            {
                Vector2Logic vertexOfTarget = targetVertices[i];
                //只获取前进区域内的顶点
                //注意这里已经是按照movingRect.center为坐标原点，前进方向为y轴变换过的坐标了
                //如果y在移动范围内
                //已转换到movingRect中心为原点，移动方向为y轴的坐标系
                if (vertexOfTarget.y > movingRect.minY && vertexOfTarget.y < maxMoveTopY)
                {
                    //获取targetRect4个顶点里离x轴最近的顶点
                    if (vertexOfTarget.y < minY)
                    {
                        minY = vertexOfTarget.y;
                        minVertex = vertexOfTarget;
                    }
                }
            }
            //由于上面的遍历排除了targetRect所有顶点都小于movingRect最小值，以及target所有顶点都大于moving最大值的情况
            //找不到最接近顶点说明属于这种情况，即不在移动范围内，直接返回没有碰撞
            if (minY == float.MaxValue)
            {
                endCenterPoint = default;
                return false;
            }
            //计算离x轴最近的交点到x轴的垂线是否在movingRect左右两条边之内（已转换到movingRect中心为原点，移动方向为y轴的坐标系）
            bool isWithinSideEdge = minVertex.x >= movingRect.minX && minVertex.x <= movingRect.maxX;
            if (isWithinSideEdge)
            {
                //基于movingRect自身坐标系计算碰撞时movingRect的center。
                endPointInMovingRectAxis = new Vector2Logic(0, minVertex.y - halfHeight);
                //还原到世界坐标系
                endCenterPoint = movingRect.RevertVectorToOrigin(endPointInMovingRectAxis);
                return true;
            }

            //情况2：
            //如果离x轴最近的顶点不再移动范围内，碰撞时将是movingRect最前面的两个顶点之一与targetRect某条边碰撞，这种情况比较复杂，要遍历所有可能的交点
            //movingRect左右两条边的延长线（范围：从当前底边到移动到maxDistance时的顶边）
            Vector2Logic leftEdgeStart = new Vector2Logic(movingRect.minX, movingRect.minY);
            Vector2Logic leftEdgeEnd = new Vector2Logic(movingRect.minX, movingRect.maxY + moveDistance);
            Vector2Logic rightEdgeStart = new Vector2Logic(movingRect.maxX, movingRect.minY);
            Vector2Logic rightEdgeEnd = new Vector2Logic(movingRect.maxX, movingRect.maxY + moveDistance);
            int len = targetVertices.Length;
            //取y最小的交点（已转换到movingRect中心为原点，移动方向为y轴的坐标系）
            minY = float.MaxValue;
            Vector2Logic minYIntersection = default;
            Vector2Logic intersection;
            //遍历四条边求交点
            for (int i = 0; i < targetVertices.Length; i++)
            {
                Vector2Logic lineStart = targetVertices[i];
                Vector2Logic lineEnd = targetVertices[(i + 1) % len];
                //优化性能，如果target这条边的两个顶点同时都在移动范围的左边或右边或上边或下边或都在整个区域中间，不判断相交
                if (!(
                    (lineStart.y < movingRect.minY && lineEnd.y < movingRect.minY) //两个顶点都在一侧，一共四条边
                    || (lineStart.y > maxMoveTopY && lineEnd.y > maxMoveTopY)
                    || (lineStart.x < movingRect.minX && lineEnd.x < movingRect.minX)
                    || (lineStart.x > movingRect.maxX && lineEnd.x > movingRect.maxX)

                    || (lineStart.y > movingRect.minY && lineStart.y < maxMoveTopY
                        && lineStart.x > movingRect.minX && lineStart.x < movingRect.maxX
                        && lineEnd.y > movingRect.minY && lineEnd.y < maxMoveTopY
                        && lineEnd.x > movingRect.minX && lineEnd.x < movingRect.maxX) //两个顶点都在区域内部
                    ))
                {
                    //计算左边延长线交点
                    if (GetLinesIntersection(leftEdgeStart, leftEdgeEnd, lineStart, lineEnd,
                        out intersection))
                    {
                        if (intersection.y < minY)
                        {
                            minYIntersection = intersection;
                            minY = intersection.y;
                        }
                    }
                    //计算右边延长线交点（注意左右两边可能同时和targetRect的边相交，如果targetRect足够大的话）所以这里不能用if else
                    if (GetLinesIntersection(rightEdgeStart, rightEdgeEnd, lineStart, lineEnd,
                        out intersection))
                    {
                        if (intersection.y < minY)
                        {
                            minYIntersection = intersection;
                            minY = intersection.y;
                        }
                    }
                }
            }
            if (minY == float.MaxValue) //没有交点
            {
                endCenterPoint = default;
                return false;
            }
            //有交点
            //基于movingRect自身坐标系计算碰撞时movingRect的center。
            endPointInMovingRectAxis = new Vector2Logic(0, minYIntersection.y - halfHeight);
            //还原到世界坐标系
            endCenterPoint = movingRect.RevertVectorToOrigin(endPointInMovingRectAxis);
            return true;
        }

        /// <summary>
        /// 获得两条线段的交点
        /// </summary>
        /// <param name="line1Start"></param>
        /// <param name="line1End"></param>
        /// <param name="line2Start"></param>
        /// <param name="line2End"></param>
        /// <param name="intersection"></param>
        /// <returns></returns>
        public static bool GetLinesIntersection(Vector2Logic line1Start, Vector2Logic line1End, Vector2Logic line2Start, Vector2Logic line2End, out Vector2Logic intersection)
        {
            Vector2Logic line1 = line1End - line1Start;
            Vector2Logic line2 = line2End - line2Start;

            //line1 X line2(也是 -line2 X line1)
            float cross = line1.x * line2.y - line1.y * line2.x;
            if (Math.Abs(cross) <= float.Epsilon) // 如果两条线段平行
            {
                intersection = default;
                return false; // 没有交点
            }

            //这里是方程组p1 = line1Start + t * line1，p2 = line2Start + u * line2，p1=p2的求解过程的最终步骤
            //当p1=p2时即代表交点，得到line1Start - line2Start = diff = u * line2 - t * line1，再利用叉乘的分配率和v1 x v1 = 0的特性求解，
            //具体是等式两边同时 x line1，可以约去t，同时 x line2，可以约去u，从而直接求得t和u，而t和u代表了交点在线段上的比例，所以如果t和u都处于0到1之间的话，就说明有交点。
            Vector2Logic diff = line1Start - line2Start;
            float t = (diff.x * line2.y - diff.y * line2.x) / -cross;
            float u = (diff.x * line1.y - diff.y * line1.x) / -cross;

            if (t >= 0 && t <= 1 && u >= 0 && u <= 1) // 如果交点在两条线段上
            {
                intersection = line1Start + t * line1; // 返回交点
                return true;
            }

            intersection = default;
            return false; // 否则没有交点
        }
    }
}
